﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace Citrix.XaAdminMobile.Util
{
    /// <summary>
    /// This panel is used to layout a title bar in a horizontal stack. It has special centering
    /// behaviour for a designated center/title element. All other elements are laid out like a
    /// stack panel.
    /// 
    /// The elements to the left of the center element are stacked from left to right like a
    /// stack panel. Similarly elements to the right of the center element are stacked, but from
    /// right to left.
    /// 
    /// The title element is displayed in any remaining space between the left and right hand
    /// stacks. The panel will also attempt to keep this element centered. Specifically it will
    /// try to center it according to the TOTAL width of the panel, not just the space available
    /// to the title element.
    /// </summary>
    internal class TitleLayoutPanel : Panel
    {
        /// <summary>
        /// Used to flag one of the child elements as the title element. The panel will try to
        /// keep this element centred in the middle of the total width of the panel, including
        /// other elements.
        /// 
        /// Note: if you flag multiple elements as a title element, only the top one will
        /// receive the special centering behaviour.
        /// </summary>
        public static readonly DependencyProperty IsTitleProperty =
            DependencyProperty.RegisterAttached("IsTitle", typeof(bool), typeof(TitleLayoutPanel),
                new FrameworkPropertyMetadata((bool)false));

        /// <summary>
        /// Gets the value of the IsTitle property for the specified object.
        /// </summary>
        public static bool GetIsTitle(DependencyObject d)
        {
            return (bool)d.GetValue(IsTitleProperty);
        }

        /// <summary>
        /// Sets the value of the IsTitle property for the specified object.
        /// </summary>
        public static void SetIsTitle(DependencyObject d, bool value)
        {
            d.SetValue(IsTitleProperty, value);
        }

        /// <summary>
        /// MeasureOverride implementation.
        /// </summary>
        /// <param name="availableSize">Size available to the panel.</param>
        /// <returns>The size we would like to use to layout our children.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            UIElement titleChild = null;
            Size desired = new Size(0, 0);

            // We give children infinite width so they tell us their preferred width. However
            // we do cap the height to whatever we have been provided with.
            Size size = new Size(Double.PositiveInfinity, availableSize.Height);

            // Get the total width all children would like to occupy. We also track the what the
            // biggest desired height is.
            foreach (UIElement child in this.Children)
            {
                bool isTitle = GetIsTitle(child);
                if (isTitle)
                {
                    titleChild = child;
                }

                child.Measure(size);
                desired.Width += child.DesiredSize.Width;
                desired.Height = Math.Max(desired.Height, child.DesiredSize.Height);
            }

            // If we are trying to layout in a width greater than what we have, then we take
            // space away from the title element if one is available.
            if ((desired.Width > availableSize.Width) && (null != titleChild))
            {
                double diff = desired.Width - availableSize.Width;
                diff = Math.Min(diff, titleChild.DesiredSize.Width);

                // So reduce the total desired width...
                desired.Width -= diff;

                // ...and re-measure the title element with the reduced width.
                titleChild.Measure(new Size(titleChild.DesiredSize.Width - diff, availableSize.Height));
            }

            return desired;
        }

        /// <summary>
        /// ArrangeOverride implementation.
        /// </summary>
        /// <param name="finalSize">The final size available to layout the panel's children.</param>
        /// <returns>The Size we ended up using to layout our children.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            UIElement centredChild = null;

            Size remaining = finalSize;
            Point leftHandOffset = new Point(0, 0);
            Point rightHandOffset = new Point(finalSize.Width, 0);

            //
            // Layout the left hand elements in a stack from left to right. We stop once we
            // find a child element that is the title element.
            //
            int index = 0;
            for (; index < this.Children.Count; ++index)
            {
                UIElement child = this.Children[index];

                bool centred = GetIsTitle(child);
                if (centred)
                {
                    centredChild = child;
                    break;
                }

                Size desired = child.DesiredSize;
                Size available = new Size();
                available.Width = Math.Min(remaining.Width, desired.Width);
                available.Height = remaining.Height;

                Rect childLayout = new Rect(leftHandOffset, available);
                child.Arrange(childLayout);

                remaining.Width -= available.Width;
                leftHandOffset.X += available.Width;
            }

            //
            // Layout the right hand elements in a stack from right to left.
            //
            int reverseIndex = this.Children.Count - 1;
            for (; reverseIndex > index; --reverseIndex)
            {
                UIElement child = this.Children[reverseIndex];

                Size desired = child.DesiredSize;
                Size available = new Size();
                available.Width = Math.Min(remaining.Width, desired.Width);
                available.Height = finalSize.Height;
                rightHandOffset.X -= available.Width;

                Rect childLayout = new Rect(rightHandOffset, available);
                child.Arrange(childLayout);

                remaining.Width -= available.Width;
            }

            //
            // Finally layout the element tagged as the centre/title element in the remaining
            // space, trying to keep it centered across the total available width.
            //
            if (centredChild != null)
            {
                Size desiredSize = centredChild.DesiredSize;

                Rect childLayout;
                if (desiredSize.Width >= remaining.Width)
                {
                    // If we don't have enough space to fit the entire item we just force it
                    // into our remaining space.
                    childLayout = new Rect(leftHandOffset, remaining);
                }
                else
                {
                    // Get the X offset that centres the title across the total width
                    double desiredXOffset = (finalSize.Width - desiredSize.Width) / 2;

                    // Check whether this offset would cause the title item to overlap the right
                    // hand stack of elements and adjust accordingly.
                    double rightStackOvershoot = Math.Max(0, (desiredXOffset + desiredSize.Width) - rightHandOffset.X);
                    double actualXOffset = desiredXOffset - rightStackOvershoot;

                    // Similarly check whether the offset would cause an overlap of the left
                    // hand stack and adjust.
                    actualXOffset = Math.Max(leftHandOffset.X, actualXOffset);

                    childLayout = new Rect(actualXOffset, 0, desiredSize.Width, finalSize.Height);
                }
                centredChild.Arrange(childLayout);
            }

            return finalSize;
        }
    }
}
